/**
 * Metronome - by Arseniy Lartsev <receive-spam@yandex.ru>
 * You may do whatever you want with this code
 */

#include <portaudio.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <unistd.h>
#include "click_raw.h"

struct MetronomeData {
	int zero_bytes;
	int position;
};

int min(int a, int b) {return (a < b) ? a : b;}

static int MetronomeCallback(const void *input, void *output,
  unsigned long buf_frames, const PaStreamCallbackTimeInfo *timeInfo,
  PaStreamCallbackFlags flags, void *usrdata)
{
	MetronomeData *data = (MetronomeData *)usrdata;
	int click_length = sizeof(click_raw_data);
	int pos = data->position;
	int bytes_remaining = buf_frames*2;
	while (bytes_remaining > 0) {
		int n;
		if (pos < click_length)
			n = click_length - pos;
		else
			n = click_length + data->zero_bytes - pos;
		if (n > bytes_remaining)
			n = bytes_remaining;
		if (pos < click_length)
			memmove(output, click_raw_data + pos, n);
		else
			memset(output, 0, n);
		output = (uint8_t *)output + n;
		bytes_remaining -= n;
		pos = (pos + n) % (click_length + data->zero_bytes);
	}
	data->position = pos;
	return 0;
}

PaStream *Play(int click_per_minute, const char **errormsg, const PaStreamParameters *param)
{
	enum {
		SAMPLE_RATE = 44100,
		BUFFER_FRAMES = 1024
	};
	float period = 60.0/click_per_minute; // seconds
	int sample_per_period = round(period*SAMPLE_RATE);
	int zero_samples = sample_per_period - sizeof(click_raw_data)/2;
	
	
	PaStream *stream = NULL;
	static MetronomeData data;
	data.zero_bytes = zero_samples*2;
	data.position = 0;
	
	PaError err = Pa_OpenStream(&stream, NULL, param, 44100,
	  BUFFER_FRAMES, paClipOff, MetronomeCallback, &data);
	if (err != paNoError)
		goto error;

	err = Pa_StartStream(stream);
	if (err != paNoError)
		goto error;
	if (errormsg != NULL)
		*errormsg = NULL;
	return stream;
    error:
	if (stream != NULL) {
		Pa_CloseStream(stream);
		if (errormsg != NULL)
			*errormsg = Pa_GetErrorText(err);
		return NULL;
	}
}

int main (int argc, char *argv[])
{
	bool use_oss = false;
	for (int i = 1; i < argc; i++) {
		if (!strcmp(argv[i], "-help") || !strcmp(argv[i], "--help")) {
			printf("Usage: metronome [-oss]\n");
			return 0;
		} else if (!strcmp(argv[i], "-oss"))
			use_oss = true;
	}
	PaError err = Pa_Initialize();
	if (err != paNoError) {
		printf("Error initializing PortAudio: %s\n", Pa_GetErrorText(err));
		return 1;
	}
        PaHostApiIndex apiindex = Pa_HostApiTypeIdToHostApiIndex(use_oss ? paOSS : paALSA);
        const PaHostApiInfo *api_info = Pa_GetHostApiInfo(apiindex);
        if (api_info == NULL) {
                printf("Sound API not available");
                return 1;
        }
        
        PaStreamParameters param;
        param.device = api_info->defaultOutputDevice;
        const PaDeviceInfo *devinfo = Pa_GetDeviceInfo(param.device);
        if (devinfo == NULL) {
                printf("No output device found\n");
                return 1;
        }
        param.channelCount = 1;
        param.sampleFormat = paInt16;
        param.suggestedLatency = devinfo->defaultLowOutputLatency;
        param.hostApiSpecificStreamInfo = NULL;
	PaStream *stream = NULL;
	while (1) {
		int cpm = 0;
		printf("Clicks per minute: ");
		scanf("%d", &cpm);
		if (stream != NULL)
			Pa_CloseStream(stream);
		if ((cpm > 0) && (cpm <= 240)) {
			char *errormsg = NULL;
			stream = Play(cpm, (const char **)&errormsg, &param);
			if (stream == NULL)
				printf("Error starting playback: %s\n", errormsg);
		}
	}
	Pa_Terminate();
}
